#include "memCached.h"
static pthread_rwlock_t semCache = PTHREAD_RWLOCK_INITIALIZER;
static diccionario_t diccionario = { NULL, NULL };
static uint8_t algoritmoEleccion = ALGORITMO_WORST_FIT;
static uint8_t algoritmoReemplazo = ALGORITMO_FIFO;

void setearAlgoritmos(uint8_t algElecc, uint8_t algReem) {
	algoritmoEleccion = algElecc;
	algoritmoReemplazo = algReem;
}

char* CUCA_get(memcached_st *ptr, const char *key, size_t key_length, size_t *value_length, uint32_t *flags, memcached_return_t *error) {
	pthread_rwlock_rdlock(&semCache);
	//busco un valor por la key, si lo encuentro lo devuelvo.
	//caso contrario devuelvo codigo de error
	memCache_t* aux = diccionario.listaCache;
	uint8_t encontrado = 0;
	while (!encontrado && aux != NULL) {
		if (strcmp(key, aux->key) == 0) {
			encontrado = 1;
		} else {
			aux = aux->sig;
		}
	}
	char* info;
	if (encontrado) {
		if (algoritmoReemplazo == ALGORITMO_LRU) {
			aux->tiempo = time(NULL);
		}
		info = leerCache(diccionario.cache, aux->posInicio * CHUNKSIZE, aux->tamanio * CHUNKSIZE);
		*error = MEMCACHED_SUCCESS;
	} else {
		info = NULL;
		*error = MEMCACHED_DATA_DOES_NOT_EXIST;
	}
	pthread_rwlock_unlock(&semCache);
	return info;
}

memcached_return_t CUCA_set(memcached_st *ptr, const char *key, size_t key_length, const char *value, size_t value_length, time_t expiration, uint32_t flags) {
	/*que pasa si se intenta escribir un dato que ya se encuentra en la cache?????*/
	pthread_rwlock_wrlock(&semCache);
	//almaceno un valor y lo asocio a su key
	div_t division = div(value_length, CHUNKSIZE);
	uint32_t cantChunks = division.quot;
	if (division.rem > 0) {
		cantChunks++;
	}
	memcached_return_t retorno;
	if (algoritmoEleccion == ALGORITMO_WORST_FIT) {
		memCache_t* espacioASetear = worstFit(diccionario.listaCache, cantChunks);
		if (espacioASetear == NULL) {
			compactarCache(diccionario.cache, &(diccionario.listaCache));
			espacioASetear = worstFit(diccionario.listaCache, cantChunks);
			if (espacioASetear == NULL) {
				/*hay que eliminar nodos para hacer espacio*/
				retorno = MEMCACHED_FAILURE;
			} else {
				cargarCache(espacioASetear, cantChunks, key, diccionario, value);
				retorno = MEMCACHED_SUCCESS;
			}
		} else {
			cargarCache(espacioASetear, cantChunks, key, diccionario, value);
			retorno = MEMCACHED_SUCCESS;
		}
	} else if (algoritmoEleccion == ALGORITMO_NEXT_FIT) {
		memCache_t* espacioASetear = NextFit(diccionario.listaCache, cantChunks);
		if (espacioASetear == NULL) {
			compactarCache(diccionario.cache, &(diccionario.listaCache));
			espacioASetear = NextFit(diccionario.listaCache, cantChunks);
			if (espacioASetear == NULL) {
				/*hay que eliminar nodos para hacer espacio*/
				retorno = MEMCACHED_FAILURE;
			} else {
				cargarCache(espacioASetear, cantChunks, key, diccionario, value);
				retorno = MEMCACHED_SUCCESS;
			}
		} else {
			cargarCache(espacioASetear, cantChunks, key, diccionario, value);
			retorno = MEMCACHED_SUCCESS;
		}
	}
	pthread_rwlock_unlock(&semCache);
	return retorno;
}

memcached_return_t CUCA_remove(memcached_st *ptr, const char *key, size_t key_length, time_t expiration) {
	pthread_rwlock_wrlock(&semCache);
	//busco un valor y lo elimino, sacando su key también
	memcached_return_t retorno;
	memCache_t* victimaAnt = NULL;
	memCache_t* victima = diccionario.listaCache;
	while (victima != NULL && strcmp(victima->key, key) != 0) {
		victimaAnt = victima;
		victima = victima->sig;
	}
	if (strcmp(victima->key, key) == 0) {
		if (victima == diccionario.listaCache) {
			victima->estado = 'L';

			if (victima->sig != NULL && victima->sig->estado == 'L') {
				unirNodosLibres(victima, &(victima->sig));
				free(victima->key);
				victima->key = NULL;
			}
		} else {
			victima->estado = 'L';
			if (victima->sig != NULL && victima->sig->estado == 'L') {
				unirNodosLibres(victima, &(victima->sig));
				free(victima->key);
				victima->key = NULL;
			}
			if (victimaAnt->estado == 'L') {
				unirNodosLibres(victimaAnt, &(victima));
			}
		}
		retorno = MEMCACHED_SUCCESS;
	}else{
		retorno = MEMCACHED_DATA_DOES_NOT_EXIST;
	}
	pthread_rwlock_unlock(&semCache);
	return retorno;
}

memcached_return_t CUCA_flush(memcached_st *ptr, time_t expiration) {
	pthread_rwlock_wrlock(&semCache);
	/* recorro toda la cache, haciendo remove
	 * sobre cada valor encontrado
	 */
	memCache_t* aux;
	uint32_t tamanioCache = 0;
	while (diccionario.listaCache != NULL) {
		aux = diccionario.listaCache;
		tamanioCache += aux->tamanio;
		diccionario.listaCache = diccionario.listaCache->sig;
		if (aux->key != NULL) {
			free(aux->key);
		}
		free(aux);
	}
	diccionario.listaCache = inicializarCache(tamanioCache);
	pthread_rwlock_unlock(&semCache);
	return MEMCACHED_SUCCESS;
}

void DumpearCache(){
	uint8_t i = 0;
	memCache_t *aux = diccionario.listaCache;
	while(aux != NULL){
		printf("particion %d: .", i);
		//convertir a hexa
		if(aux->estado == 'O'){
			printf("Ocupada.");
		} else {
			printf("Libre.");
		}
		printf("Size: %d bytes.", aux->tamanio*CHUNKSIZE);
		if(aux->estado == 'O'){
			printf("Key: \"%s\" \n", aux->key);
		}
		aux = aux->sig;
	}
	return;
}
